package com.dashan.backend.geosearch;

import static com.dashan.backend.geosearch.GeoMath.lat2MapY;
import static com.dashan.backend.geosearch.GeoMath.lon2MapX;
import static com.dashan.backend.geosearch.GeoMath.mapX2Lon;
import static com.dashan.backend.geosearch.GeoMath.mapY2Lat;
import static java.lang.Math.PI;
import static java.lang.Math.max;
import static java.lang.Math.min;

import java.util.ArrayList;

import com.dashan.backend.DashanException;

public class BoundingBox {
    
    private double mapXFrom;
    private double mapXTo;
    private double mapYFrom;
    private double mapYTo; 
    private int level;
    
    public BoundingBox(double top, double bottom, double right, double left) {
        
        double from = lon2MapX(left);
        double to = lon2MapX(right);
        this.mapXFrom = min(from, to);
        this.mapXTo = max(from, to);
        
        from = lat2MapY(top);
        to  = lat2MapY(bottom);
        this.mapYFrom = max(from, to);
        this.mapYTo = min(from, to);
        
        double span = max(mapXTo - mapXFrom, mapYTo - mapYFrom);
        this.level = Long.numberOfTrailingZeros(Long.highestOneBit((long) ((2 * PI) / span))) + 1;
    }
    
    /** Get the approximate level of this box. */
    public int getLevel() {
        return level;
    }
    
    public double getCenterLon() {
        return mapX2Lon(getCenterMapX());
    }
    
    public double getCenterMapX() {
        return (mapXFrom + mapXTo) / 2;
    }
    
    public double getCenterLat() {
        return mapY2Lat(getCenterMapY());
    }
    
    public double getCenterMapY() {
        return (mapYFrom + mapYTo) / 2;
    }
    
    public GeoCell[] getCoveringCells() throws DashanException {
        GeoCell lt = new GeoCell(level, mapXFrom, mapYFrom);
        GeoCell rb = new GeoCell(level, mapXTo, mapYTo);
        return interpolateCells(lt, rb);
    }
    
    public boolean isInside(double lon, double lat) {
        double mapX = lon2MapX(lon);
        double mapY = lat2MapY(lat);
        return (mapXFrom < mapX && mapX < mapXTo) 
            && (mapYFrom > mapY && mapY > mapYTo);
    }
    
    public static GeoCell[] interpolateCells(GeoCell upperLeftCell, GeoCell bottomRightCell) 
        throws DashanException {
        if (upperLeftCell.getLevel() > bottomRightCell.getLevel()) {
            upperLeftCell = upperLeftCell.castToBiggerCell(bottomRightCell.getLevel());
        } else if (upperLeftCell.getLevel() < bottomRightCell.getLevel()) {
            bottomRightCell = bottomRightCell.castToBiggerCell(upperLeftCell.getLevel());
        }
        return interpolateCells(upperLeftCell.getLevel(), 
                upperLeftCell.getIndexX(), bottomRightCell.getIndexX(), 
                upperLeftCell.getIndexY(), bottomRightCell.getIndexY());
    }
    
    private static GeoCell[] interpolateCells(int level, int xFrom, int xTo, int yFrom, int yTo) {
        if (xTo < xFrom || yTo < yFrom) {
            return new GeoCell[0];
        }
        
        ArrayList<GeoCell> cells = new ArrayList<GeoCell>((xTo - xFrom + 1) * (yTo - yFrom + 1));
        for (int x = xFrom; x <= xTo; x++) {
            for (int y = yFrom; y <= yTo; y++) {
                cells.add(new GeoCell(level, x, y));
            }
        }
        return cells.toArray(new GeoCell[0]);
    }
}
